package xj.meta;

/**********************************************************************************
 *
 * @author Xiaoping Jia
 * @version 0.2
 *
 * Created on: 2005/05/01
 * Requires: J2SE 5.0
 *
 **********************************************************************************/

import static xj.util.xml.XMLUtil.convertXMLEscapeChar;
import static xj.util.xml.XMLUtil.getXMLAttributeString;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;

public abstract class AbstractModelElement 
  extends AbstractElement 
  implements ModelElement {

  public String getElementName() {
    if (elementName != null) {
      return elementName;
    } else {
      String name = getClass().getName();
      int i = name.lastIndexOf('.');
      if (i >= 0) {
	return name.substring(i + 1);
      } else {
	return name;
      }
    }
  }

  public void setElementName(String elementName) {
    this.elementName = elementName;
  }

  public String getRole() {
    return role;
  }

  public void setRole(String role) {
    this.role = role;
  }

  public ModelElement getOwner() {
    return owner;
  }

  public void setOwner(ModelElement owner) {
    this.owner = owner;
  }

  public List<ModelElement> getOwnedElements() {
    return ownedElements;
  }

  public void addOwnedElement(ModelElement e) {
    if (e != null) {
      e.setOwner(this);
      ownedElements.add(e);
    }
  }

  public void addOwnedElement(ModelElement e, String role) {
    if (e != null) {
      e.setRole(role);
      e.setOwner(this);
      ownedElements.add(e);
    }
  }

  public void setOwnedElements(String role, List<ModelElement> l) {
    removeOwnedElements(role);
    if (l != null) {
      Iterator<ModelElement> iter = l.iterator();
      while (iter.hasNext()) {
	ModelElement e = iter.next();
	addOwnedElement(e, role);
      }
    }
  }

  public void setOwnedElement(String role, ModelElement e) {
    removeOwnedElements(role);
    addOwnedElement(e, role);
  }

  public void removeOwnedElement(ModelElement e) {
    if (e != null) {
      ownedElements.remove(e);
    }
  }

  public void removeOwnedElements(String role) {
    Iterator<ModelElement> iter = ownedElements.iterator();
    while (iter.hasNext()) {
      ModelElement e = iter.next();
      if (e != null) {
	if (role == null && e.getRole() == null || 
	    role != null && role.equals(e.getRole())) {
	  iter.remove();
	}
      }
    }
  }

  /**
   * Deconstruct all the elements owned by this element. All descendants
   * are disconnected from their parents. Warning: structure/informtaion
   * may be lost
   * 
   * Use destructable/preserved to control the deconstruction Only
   * elements corresponding to diagrams and hypernodes should be
   * destructable Elements other than graph nodes/edges should be
   * preserved
   */
  public void deconstruct() {
    if (debug) {
      System.out.println("AbstractModelElement.deconstruct() " + 
			 getElementName() + " " + destructable + " " + preserved);
    }

    if (isDestructable()) {
      Iterator<ModelElement> iter = ownedElements.iterator();
      while (iter.hasNext()) {
	ModelElement e = iter.next();

	if (debug) {
	  System.out.println("AbstractModelElement.deconstruct() child " + 
			     e.getElementName() + " " + 
			     e.isDestructable() + " " + 
			     e.isPreserved());
	}

	if (e != null) {
	  if (e instanceof AssociationElement) {
	    iter.remove();
	  } else if (e instanceof ReferenceElement) {
	    continue;
	  } else {
	    if (!e.isPreserved()) {
	      if (e.isDestructable()) {
		e.deconstruct();
	      }
	      iter.remove();
	    }
	  }
	}
      }
    }
  }

  /** whether the element should be deconstructed */
  public boolean isDestructable() {
    return destructable;
  }

  public void setDestructable(boolean b) {
    destructable = b;
  }

  /** whether the element should be preserved in deconstruction */
  public boolean isPreserved() {
    return preserved;
  }

  public void setPreserved(boolean b) {
    preserved = b;
  }

  public List<ModelElement> getElements(String role) {
    List<ModelElement> result = new ArrayList<ModelElement>();
    Iterator<ModelElement> iter = ownedElements.iterator();
    while (iter.hasNext()) {
      ModelElement e = iter.next();
      if (e != null) {
	if (role == null && e.getRole() == null || 
	    role != null && role.equals(e.getRole())) {
	  result.add(e);
	}
      }
    }
    return result;
  }

  public boolean isChild(ModelElement e) {
    Iterator<ModelElement> iter = ownedElements.iterator();
    while (iter.hasNext()) {
      ModelElement c = iter.next();
      if (c == e) {
	return true;
      }
    }
    return false;
  }

  public boolean isDescendant(ModelElement e) {
    Iterator<ModelElement> iter = ownedElements.iterator();
    while (iter.hasNext()) {
      ModelElement c = iter.next();
      if (c == e || c.isDescendant(e)) {
	return true;
      }
    }
    return false;
  }

  /** e is an ancestor of this */
  public boolean isAncestor(ModelElement e) {
    if (e != null) {
      ModelElement p = getOwner();
      if (p == e) {
	return true;
      } else if (p != null) {
	return p.isAncestor(e);
      }
    }
    return false;
  }

  public Set<String> getRoleNames() {
    Set<String> result = new HashSet<String>();
    Iterator<ModelElement> iter = ownedElements.iterator();
    while (iter.hasNext()) {
      ModelElement e = iter.next();
      if (e != null && e.getRole() != null) {
	result.add(e.getRole());
      }
    }
    return result;
  }

  public Object getAttribute(String attr) {
    if (attr != null) {
      return attributes.get(attr);
    }
    return null;
  }

  public void setAttribute(String attr, Object value) {
    if (attr != null) {
      if (value != null) {
	attributes.put(attr, value);
      } else { 
	attributes.remove(attr);
      }
    }
  }

  public Set<String> getAllAttirbuteNames() {
    return attributes.keySet();
  }

  public Set<Annotation> getAnnotations() {
    Set<Annotation> result = new HashSet<Annotation>();
    Collection<Annotation> c = annotations.values();
    if (c != null) {
      Iterator<Annotation> iter = c.iterator();
      while (iter.hasNext()) {
	result.add(iter.next());
      }
    }
    return result;
  }

  public void addAnnotation(Annotation a) {
    if (a != null) {
      String name = a.getName();
      if (name != null) {
	annotations.put(name, a);
	addStereotype(name);
      }
    }
  }

  public void addAnnotations(List<Annotation> alist) {
    if (alist != null) {
      Iterator<Annotation> iter = alist.iterator();
      while (iter.hasNext()) {
	addAnnotation(iter.next());
      }
    }
  }

  public Annotation getAnnotation(String name) {
    if (name != null) {
      return annotations.get(name);
    }
    return null;
  }

  public void removeAnnotation(String name) {
    if (name != null) {
      annotations.remove(name);
      removeStereotype(name);
    }
  }

  public Set<Link> getLinks() {
    Set<Link> result = new HashSet<Link>();
    Collection<Link> c = links.values();
    if (c != null) {
      Iterator<Link> iter = c.iterator();
      while (iter.hasNext()) {
	result.add(iter.next());
      }
    }
    return result;
  }

  public void addLink(String name, String link) {
    if (name != null) {
      Link l = new Link(name, link);
      links.put(name, l);
    }
  }

  public void addLink(Link a) {
    if (a != null) {
      String name = a.getName();
      if (name != null) {
	links.put(name, a);
      }
    }
  }

  public void addLinks(List<Link> alist) {
    if (alist != null) {
      Iterator<Link> iter = alist.iterator();
      while (iter.hasNext()) {
	addLink(iter.next());
      }
    }
  }

  public Link getLink(String name) {
    if (name != null) {
      return links.get(name);
    }
    return null;
  }

  public void removeLink(String name) {
    if (name != null) {
      links.remove(name);
    }
  }

  public void removeAllLinks() {
    links.clear();
  }

  public boolean isEmptyMark() {
    if (mark != null) {
      return mark.isEmptyMark();
    }
    return true;
  }

  public Set<String> getStereotypes() {
    if (mark != null) {
      return mark.getStereotypes();
    } else {
      return null;
    }
  }

  public void setStereotypes(Set<String> st) {
    if (mark != null) {
      mark.setStereotypes(st);
    }
  }

  public void addStereotype(String s) {
    if (mark != null) {
      mark.addStereotype(s);
    }
  }

  public void addStereotypes(Set<String> st) {
    if (mark != null) {
      mark.addStereotypes(st);
    }
  }

  public void removeStereotype(String s) {
    if (mark != null) {
      mark.removeStereotype(s);
    }
  }

  public void removeStereotypes(Set<String> st) {
    if (mark != null) {
      mark.removeStereotypes(st);
    }
  }

  public boolean hasStereotype(String s) {
    if (mark != null) {
      return mark.hasStereotype(s);
    }
    return false;
  }

  public void setStereotypes(String st) {
    if (st != null) {
      Set<String> sst = new HashSet<String>();
      StringTokenizer tok = new StringTokenizer(st, " ,");
      while (tok.hasMoreTokens()) {
	sst.add(tok.nextToken());
      }
      setStereotypes(sst);
    } else {
      setStereotypes((Set<String>) null);
    }
  }

  public String getStereotypeString() {
    Set<String> st = getStereotypes();
    if (st != null && !st.isEmpty()) {
      StringBuilder sb = new StringBuilder();
      Iterator<String> iter = st.iterator();
      boolean first = true;
      while (iter.hasNext()) {
	if (first) {
	  first = false;
	} else {
	  sb.append(", ");
	}
	sb.append(iter.next());
      }
      return sb.toString();
    }
    return null;
  }

  public Map<String, String> getTags() {
    if (mark != null) {
      return mark.getTags();
    } else {
      return null;
    }
  }

  public void setTags(Map<String, String> tags) {
    if (mark != null) {
      mark.setTags(tags);
    }
  }

  public void removeAllTags() {
    if (mark != null) {
      mark.removeAllTags();
    }
  }

  public String getTagValue(String t) {
    if (mark != null) {
      return mark.getTagValue(t);
    } else {
      return null;
    }
  }

  public void setTagValue(String tag, String value) {
    if (mark != null) {
      mark.setTagValue(tag, value);
    }
  }

  public void removeTag(String t) {
    if (mark != null) {
      mark.removeTag(t);
    }
  }

  public Mark getMark() {
    return mark;
  }

  public void setMark(Mark mark) {
    this.mark = mark;
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) {
      return true;
    }
    return false;
  }

  @Override
  public int hashCode() {
    return super.hashCode();
  }

  public void exportXML(PrintWriter out, int level) {
    exportXML(out, level, null);
  }

  public void exportXML(PrintWriter out, int level, String role) {
    if (out != null) {
      String spaces = "";
      if (level > 0) {
	spaces = mkPad(level);
      }

      String ename = getElementName();
      out.print(spaces + "<" + ename);
      if (id != null) {
	out.print(' ' + getXMLAttributeString("id", id));
      }
      if (role != null && !role.equalsIgnoreCase(ename)) {
	out.print(' ' + getXMLAttributeString("role", role));
      }

      /*
       * out.print(' ' + getXMLAttributeString("preserved", preserved));
       * out.print(' ' + getXMLAttributeString("destructable", destructable));
       */

      Set<String> attributeNames = getAllAttirbuteNames();
      Iterator<String> iter = attributeNames.iterator();
      while (iter.hasNext()) {
	String attrName = iter.next();
	Object attrValue = getAttribute(attrName);
	if (isSimpleType(attrValue) && !asElement(attrName)) {
	  out.print(' ' + getXMLAttributeString(attrName, attrValue));
	}
      }
      boolean openTag = true;

      iter = attributeNames.iterator();
      while (iter.hasNext()) {
	String attrName = iter.next();
	Object attrValue = getAttribute(attrName);
	if (attrValue == null) {
	  continue;
	}
	if (isSimpleType(attrValue) && !asElement(attrName)) {
	  continue;
	}

	if (attrValue instanceof Collection) {
	  Collection c = (Collection) attrValue;
	  Iterator ci = c.iterator();
	  while (ci.hasNext()) {
	    if (openTag) {
	      out.println(">");
	      openTag = false;
	    }
	    String attrString = ci.next().toString();
	    if (attrString != null && attrString.length() > 0) {
	      out.println(spaces + " <" + attrName + ">" + 
			  convertXMLEscapeChar(attrString) + "</" + attrName + ">");
	    }
	  }
	} else {
	  String attrString = attrValue.toString();
	  if (attrString == null) {
	    continue;
	  }
	  attrString = attrString.trim();
	  if (attrString.length() == 0) {
	    continue;
	  }

	  if (openTag) {
	    out.println(">");
	    openTag = false;
	  }

	  out.print(spaces + " <" + attrName + ">");
	  if (inCDATASection(attrName)) {
	    out.print("<![CDATA[" + attrString + "]]>");
	  } else {
	    out.print(convertXMLEscapeChar(attrString));
	  }
	  out.println("</" + attrName + ">");
	}
      }

      List<ModelElement> elements = getElements(null);
      if (elements != null && elements.size() > 0) {
	Iterator<ModelElement> eiter = elements.iterator();
	while (eiter.hasNext()) {
	  if (openTag) {
	    out.println(">");
	    openTag = false;
	  }
	  ModelElement element = eiter.next();
	  element.exportXML(out, level + 1);
	}
      }

      Set<String> roleNames = getRoleNames();
      iter = roleNames.iterator();
      while (iter.hasNext()) {
	String rolename = iter.next();
	elements = getElements(rolename);
	if (elements != null && elements.size() > 0) {
	  Iterator<ModelElement> eiter = elements.iterator();
	  while (eiter.hasNext()) {
	    if (openTag) {
	      out.println(">");
	      openTag = false;
	    }
	    ModelElement element = eiter.next();
	    element.exportXML(out, level + 1, rolename);
	  }
	}
      }

      if (mark != null) {
	Set<String> stereotypes = mark.getStereotypes();
	if (stereotypes != null && !stereotypes.isEmpty()) {
	  if (openTag) {
	    out.println(">");
	    openTag = false;
	  }

	  out.println(spaces + " <" + STEREOTYPES_ELEMENT_NAME + ">");
	  Iterator<String> siter = stereotypes.iterator();
	  while (siter.hasNext()) {
	    String st = siter.next();
	    out.println(spaces + "  <" + STEREOTYPE_ELEMENT_NAME + ' ' + 
			getXMLAttributeString("name", st) + "/>");
	  }
	  out.println(spaces + " </" + STEREOTYPES_ELEMENT_NAME + ">");
	}

	Map<String, String> tags = mark.getTags();
	if (tags != null && !tags.isEmpty()) {
	  if (openTag) {
	    out.println(">");
	    openTag = false;
	  }

	  out.println(spaces + " <" + TAGS_ELEMENT_NAME + ">");
	  Iterator<Map.Entry<String, String>> titer = tags.entrySet().iterator();
	  while (titer.hasNext()) {
	    Map.Entry<String, String> tag = titer.next();
	    out.println(spaces + "  <" + TAG_ELEMENT_NAME + ' '	+ 
			getXMLAttributeString("name", tag.getKey()) + ' ' + 
			getXMLAttributeString("value", tag.getValue()) + "/>");
	  }
	  out.println(spaces + " </" + TAGS_ELEMENT_NAME + ">");
	}
      }

      if (links != null && !links.isEmpty()) {
	if (openTag) {
	  out.println(">");
	  openTag = false;
	}

	Iterator<Link> liter = links.values().iterator();
	while (liter.hasNext()) {
	  Link link = liter.next();
	  out.println(spaces + "  <" + LINK_ELEMENT_NAME + ' '  + 
		      getXMLAttributeString("xml:link", "simple") + ' ' + 
		      getXMLAttributeString("name", link.getName()) + ' ' + 
		      getXMLAttributeString("href", link.getLink()) + "/>");
	}
      }

      if (openTag) {
	out.println("/>");
      } else {
	out.println(spaces + "</" + ename + ">");
      }
    }
  }

  public static final String STEREOTYPES_ELEMENT_NAME = "Stereotypes";

  public static final String STEREOTYPE_ELEMENT_NAME = "Stereotype";

  public static final String TAGS_ELEMENT_NAME = "Tags";

  public static final String TAG_ELEMENT_NAME = "Tag";

  public static final String LINK_ELEMENT_NAME = "Link";

  protected boolean asElement(String attrName) {
    return false;
  }

  protected boolean inCDATASection(String name) {
    return false;
  }

  protected static boolean isSimpleType(Object o) {
    return (o instanceof Boolean || 
	    o instanceof Character || 
	    o instanceof Number || 
	    o instanceof String || 
	    o instanceof Date || 
	    o instanceof Enum);
  }

  protected static boolean initMode = false;

  public static void setInitMode(boolean initMode) {
    AbstractModelElement.initMode = initMode;
  }

  @Override
  public void setId(String id) {
    ModelRepository repository = ModelRepository.getInstance();
    repository.removeElement(this);
    this.id = id;
    repository.addElement(this);
  }

  @Override
  public Object clone() throws CloneNotSupportedException {
    AbstractModelElement e = (AbstractModelElement) super.clone();
    e.ownedElements = new ArrayList<ModelElement>();
    e.attributes = new HashMap<String, Object>();
    e.annotations = new HashMap<String, Annotation>();
    e.mark = new MarkImpl();
    e.destructable = destructable;
    e.preserved = preserved;
    e.showMetaDetail = showMetaDetail;
    e.owner = null;

    if (!initMode && !ModelRepository.isTransientMode()) {
      ModelRepository repository = ModelRepository.getInstance();
      repository.addElement(e);
    }

    if (debug) {
      System.out.println("AbstractModelElement.clone(): " + e.getClass().getName() + " " + e.getId());
    }

    return e;
  }

  protected AbstractModelElement() {
    destructable = true;
    preserved = false;

    if (!initMode && !ModelRepository.isTransientMode()) {
      ModelRepository repository = ModelRepository.getInstance();
      repository.addElement(this);
    }

    if (debug) {
      System.out.println("AbstractModelElement(): " + getClass().getName() + " " + getId());
    }
  }

  protected String ownedElementsString() {
    StringBuilder sb = new StringBuilder();
    Iterator<ModelElement> iter = ownedElements.iterator();
    while (iter.hasNext()) {
      ModelElement e = iter.next();
      sb.append(' ').append(e.getId());
    }
    return sb.toString();
  }

  protected String elementName;

  protected String role;

  protected ModelElement owner;

  protected List<ModelElement> ownedElements = new ArrayList<ModelElement>();

  protected Map<String, Object> attributes = new HashMap<String, Object>();

  protected Map<String, Annotation> annotations = new HashMap<String, Annotation>();

  protected Map<String, Link> links = new HashMap<String, Link>();

  protected Mark mark = new MarkImpl();

  protected boolean destructable = true;

  protected boolean preserved = false;

  protected boolean showMetaDetail = true;

  protected static final boolean debug = false;

  // New Added Methods by Frank
  public Set<String> getAllRoleNames() {
    Set<String> result = new HashSet<String>();
    Iterator<ModelElement> iter = ownedElements.iterator();
    while (iter.hasNext()) {
      ModelElement e = iter.next();
      if (e != null) {
	String role = e.getRole();
	if (role != null) {
	  result.add(role);
	}
      }
    }
    return result;
  }

  public ModelElement getOwnedElement(int p) {
    if (p < 0 || p >= ownedElements.size()) {
      return null;
    } else {
      return ownedElements.get(p);
    }
  }

  public void addOwnedElements(String role, List<ModelElement> l) {
    if (l != null) {
      Iterator<ModelElement> iter = l.iterator();
      while (iter.hasNext()) {
	ModelElement e = iter.next();
	e.setRole(role);
	e.setOwner(this);
	ownedElements.add(e);
      }
    }
  }

  public boolean hasOwnedElement(ModelElement e) {
    Iterator<ModelElement> iter = ownedElements.iterator();
    while (iter.hasNext()) {
      if (iter.next() == e) {
	return true;
      }
    }
    return false;
  }

  public boolean hasOwnedElement(String role) {
    Iterator<ModelElement> iter = ownedElements.iterator();
    while (iter.hasNext()) {
      ModelElement e = iter.next();
      String r = e.getRole();
      if (role == null && r == null || role != null && role.equals(r)) {
	return true;
      }
    }
    return false;
  }

  public void insertOwnedElementAt(ModelElement e, String role, int p) {
    if (e != null) {
      if (p >= 0 && p <= ownedElements.size()) {
	e.setRole(role);
	e.setOwner(this);
	ownedElements.add(p, e);
      }
    }
  }

  public void replaceOwnedElement(String newRole, ModelElement newElement,
      ModelElement oldElement) {
    if (newElement != null) {
      int p = 0;
      if (oldElement != null && hasOwnedElement(oldElement)) {
	for (int i = 0; i < ownedElements.size(); i++) {
	  if (ownedElements.get(i) == oldElement) {
	    p = i;
	    break;
	  }
	}
      }
      removeOwnedElement(oldElement);
      ownedElements.add(p, newElement);
      newElement.setRole(newRole);
      newElement.setOwner(this);
    }
  }

  public void replaceOwnedElement(ModelElement newElement, String role) {
    if (newElement != null) {
      int p = -1, i = 0;
      Iterator<ModelElement> iter = ownedElements.iterator();
      while (iter.hasNext()) {
	ModelElement e = iter.next();
	String r = e.getRole();
	if (role == null && r == null || role != null && role.equals(r)) {
	  iter.remove();
	  if (p < 0) {
	    p = i;
	  }
	}
	i++;
      }

      ownedElements.add(p, newElement);
      newElement.setRole(role);
      newElement.setOwner(this);
    }
  }

  public void replaceOwnedElement(String role, ModelElement newElement, int p) {
    if (newElement != null && p >= 0 && p <= ownedElements.size()
	&& !hasOwnedElement(newElement)) {
      ModelElement oldElement = getOwnedElement(p);
      removeOwnedElement(oldElement);
      insertOwnedElementAt(newElement, role, p);
    }
  }

  public void removeAttribute(String name) {
    attributes.remove(name);
  }

  public int getLevel() {
    ModelElement owner = getOwner();
    if (owner == null) {
      return 0;
    } else {
      return owner.getLevel() + 1;
    }
  }

  public String getRoleInOwner() {
    return getRole();
  }

  public void visitPreOrder(ModelElementVisitor visitor, Object data) {
    if (visitor != null) {
      Object result = data;
      result = this.accept(visitor, data);

      Iterator<ModelElement> iter = ownedElements.iterator();
      while (iter.hasNext()) {
	iter.next().visitPreOrder(visitor, result);
      }
    }
  }

  public void visitPreOrder(ModelElementVisitor visitor, Object data,
      boolean useMetaDetailAttribute) {
    if (!useMetaDetailAttribute) {
      visitPreOrder(visitor, data);
    } else {
      if (visitor != null) {
	Object result = data;
	result = this.accept(visitor, data);
	Iterator<ModelElement> iter = ownedElements.iterator();
	while (iter.hasNext()) {
	  ModelElement element = iter.next();
	  if (element.getShowMetaDetail()) {
	    element.visitPreOrder(visitor, result, true);
	  }
	}
      }
    }
  }

  public void visitPostOrder(ModelElementVisitor visitor, Object data) {
    if (visitor != null) {
      Object result = data;
      Iterator<ModelElement> iter = ownedElements.iterator();
      while (iter.hasNext()) {
	iter.next().visitPostOrder(visitor, data);
      }
      result = this.accept(visitor, data);
    }
  }

  public Object accept(ModelElementVisitor visitor, Object data) {
    if (visitor != null) {
      return visitor.visit(this, data);
    } else {
      return null;
    }
    // throw new MetaException("Method not implemented: " +
    // this.getClass().getName());
  }

  public boolean getShowMetaDetail() {
    return showMetaDetail;
  }

  public void setShowMetaDetail(boolean b) {
    showMetaDetail = b;
  }

  public void dump(PrintWriter out, int level) {
    String pad = mkPad(level);
    out.println(pad + "[" + level + "] Class name: " + getClass().getName());
    dumpAnnotation(out, level);
  }

  protected void dumpAnnotation(PrintWriter out, int level) {
    String pad = mkPad(level);
    Collection<Annotation> c = annotations.values();
    if (c != null) {
      Iterator<Annotation> iter = c.iterator();
      while (iter.hasNext()) {
	out.println(pad + iter.next());
      }
    }
  }

  public static String mkPad(int length) {
    char[] ca = new char[length];
    for (int i = 0; i < length; i++) {
      ca[i] = ' ';
    }
    return new String(ca);
  }

}
